/*
 * NIOS2 I2C Adapter Driver
 *
 * Copyright 2007 Kenny Chen
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, version 2 of the License.
 *
 *
 * Compile this driver with:

	echo "obj-m := nios2_i2c_adap.o" > Makefile
	make -C <path/to/kernel/src> SUBDIRS=$PWD modules
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <asm/io.h>


#define I2C_COMMON_REG			0x0
#define		I2C_COMMON_BUSY		0x1
#define		I2C_COMMON_TE			0x2
#define		I2C_COMMON_IRQ		0x4
#define		I2C_COMMON_IE			0x40
#define		I2C_COMMON_EN			0x80
#define I2C_CONTROL_REG 		0x1
#define		I2C_CONTROL_START 0x1
#define		I2C_CONTROL_STOP  0x2
#define		I2C_CONTROL_RD	  0x4
#define		I2C_CONTROL_WR	  0x0
#define		I2C_CONTROL_ACK		0x8
#define I2C_RX_BUF					0x2
#define I2C_TX_BUF					0x3

#define I2C_STATE_MASK			(I2C_COMMON_BUSY | I2C_COMMON_TE)

struct nios2_i2c {
	void __iomem *base;
	wait_queue_head_t wait;
	struct i2c_adapter adap;
	int state;
};

static inline void nios2_i2c_reg_write(struct nios2_i2c *i2c, int reg, u8 value)
{
	writeb(value, i2c->base + reg);
}

static inline u8 nios2_i2c_reg_read(struct nios2_i2c *i2c, int reg)
{
	return readb(i2c->base + reg);
}

static int nios2_i2c_wait_busy(struct nios2_i2c *i2c)
{
	volatile u8 state;
	
	state = nios2_i2c_reg_read(i2c, I2C_COMMON_REG);
			
	if(wait_event_timeout(i2c->wait, !(state & I2C_STATE_MASK), HZ))
		return (0 - i2c->state);
	else
		return -ETIMEDOUT;
}

static irqreturn_t nios2_i2c_isr(int irq, void *dev_id)
{
	struct nios2_i2c *i2c = dev_id;
	
	i2c->state = nios2_i2c_reg_read(i2c, I2C_COMMON_REG);
	i2c->state &= I2C_STATE_MASK;
	
	if(waitqueue_active(&i2c->wait))
		wake_up(&i2c->wait);

	return IRQ_HANDLED;
}

static int nios2_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	struct nios2_i2c *i2c = i2c_get_adapdata(adap);
	int i, len, ret = 0;
	
	for(i = 0; i < num; i++) {
		/* Write client addr */
		nios2_i2c_reg_write(i2c, I2C_TX_BUF, msgs[i].addr);
		/* Issue a "Start & Write" command */
		nios2_i2c_reg_write(i2c, I2C_CONTROL_REG, I2C_CONTROL_START | I2C_CONTROL_WR);
		nios2_i2c_wait_busy(i2c);
		
		if(msgs[i].flags & I2C_M_RD) {
			/* Read data from RX buffer */
			for(len = 0; len < msgs->len; len++) {
				if(!msgs[i].buf[len]) {
					ret = -EINVAL;
					break;
				}
					
				/* Issue a "Read & ACK" command */
				nios2_i2c_reg_write(i2c, I2C_CONTROL_REG, I2C_CONTROL_RD | I2C_CONTROL_ACK);
				ret = nios2_i2c_wait_busy(i2c);
				if(ret < 0)
					break;
				/* Read data */
				msgs[i].buf[len] = nios2_i2c_reg_read(i2c, I2C_RX_BUF);
			}
			
			/* Issue a "Stop & Read & NACK" command */
			nios2_i2c_reg_write(i2c, I2C_CONTROL_REG, I2C_CONTROL_STOP | I2C_CONTROL_RD);
			ret = nios2_i2c_wait_busy(i2c);
		}
		else {
			/* Write data to TX buffer */
			for(len = 0; len < msgs->len; len++) {
				if(!msgs[i].buf[len]) {
					ret = -EINVAL;
					len = msgs->len - 1;  /* make this to be the last data */
				}
				else
					nios2_i2c_reg_write(i2c, I2C_TX_BUF, msgs[i].buf[len]);
				
				if(msgs->len == len + 1) { /* The last data ? */
					/* Issue a "Stop & Write" command */
					nios2_i2c_reg_write(i2c, I2C_CONTROL_REG, I2C_CONTROL_STOP | I2C_CONTROL_WR);
				}
				else {
					/* Issue a "Write" command */
					nios2_i2c_reg_write(i2c, I2C_CONTROL_REG, I2C_CONTROL_WR);
				}
				ret = nios2_i2c_wait_busy(i2c);
				if(ret < 0)
					break;
			}
		}
		
		if(ret < 0) {
			/* Should I reset the controller ? */
			break;
		}
	}
	
	return ret;
}

static u32 nios2_i2c_func(struct i2c_adapter *adapter)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm nios2_i2c_algorithm = {
	.name						= "NIOS2 I2C Algorithm",
	.master_xfer		= nios2_i2c_xfer,
	.functionality	= nios2_i2c_func,
};

static struct i2c_adapter nios2_i2c_adapter = {
	.owner	= THIS_MODULE,
	.class	= I2C_CLASS_HWMON,
	.algo		= &nios2_i2c_algorithm,
	.name		= "NIOS2 I2C Adapter",
};

static int __devinit nios2_i2c_probe(struct platform_device *pdev)
{
	struct nios2_i2c *i2c;
	struct resource *res, *res2;
	int ret;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res)
		return -ENODEV;

	res2 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!res2)
		return -ENODEV;

	i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;

	if (!request_mem_region(res->start, res->end - res->start + 1,
				pdev->name)) {
		dev_err(&pdev->dev, "Memory region busy\n");
		ret = -EBUSY;
		goto request_mem_failed;
	}

	i2c->base = ioremap(res->start, res->end - res->start + 1);
	if (!i2c->base) {
		dev_err(&pdev->dev, "Unable to map registers\n");
		ret = -EIO;
		goto map_failed;
	}

	init_waitqueue_head(&i2c->wait);
	ret = request_irq(res2->start, nios2_i2c_isr, 0, pdev->name, i2c);
	if (ret) {
		dev_err(&pdev->dev, "Cannot claim IRQ\n");
		goto request_irq_failed;
	}
	/* Enable controller and interrupt */
	nios2_i2c_reg_write(i2c, I2C_COMMON_REG, I2C_COMMON_EN | I2C_COMMON_IE);
	
	/* hook up driver to tree */
	platform_set_drvdata(pdev, i2c);
	i2c->adap = nios2_i2c_adapter;
	i2c_set_adapdata(&i2c->adap, i2c);
	i2c->adap.dev.parent = &pdev->dev;

	/* add i2c adapter to i2c tree */
	ret = i2c_add_adapter(&i2c->adap);
	if (ret) {
		dev_err(&pdev->dev, "Failed to add adapter\n");
		goto add_adapter_failed;
	}

	return 0;

add_adapter_failed:
	free_irq(res2->start, i2c);
request_irq_failed:
	iounmap(i2c->base);
map_failed:
	release_mem_region(res->start, res->end - res->start + 1);
request_mem_failed:
	kfree(i2c);

	return ret;
}

static int __devexit nios2_i2c_remove(struct platform_device* pdev)
{
	struct ocores_i2c *i2c = platform_get_drvdata(pdev);
	struct resource *res;

	/* Disable controller and interrupt */
	nios2_i2c_reg_write(i2c, I2C_COMMON_REG, 0);

	/* remove adapter & data */
	i2c_del_adapter(&i2c->adap);
	platform_set_drvdata(pdev, NULL);

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (res)
		free_irq(res->start, i2c);

	iounmap(i2c->base);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res)
		release_mem_region(res->start, res->end - res->start + 1);

	kfree(i2c);

	return 0;
}

static struct platform_driver nios2_i2c_driver = {
	.probe  = nios2_i2c_probe,
	.remove = __devexit_p(nios2_i2c_remove),
	.driver = {
		.owner = THIS_MODULE,
		.name = "NIOS2-I2C",
	},
};

static int __init nios2_i2c_init(void)
{
	return platform_driver_register(&nios2_i2c_driver);
}

static void __exit nios2_i2c_exit(void)
{
	platform_driver_unregister(&nios2_i2c_driver);
}

MODULE_AUTHOR("Kenny Chen");
MODULE_DESCRIPTION("nios2_i2c i2c adapter");
MODULE_LICENSE("GPL");

module_init(nios2_i2c_init);
module_exit(nios2_i2c_exit);
